﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpCmd.IO;

namespace SharpCmd.Controls.Viewers
{
    internal class TextViewer : ViewerControl
    {
        private LinkedList<BufferLine> buffer;
        private UnbufferedStreamReader Reader;
        private long offset;

        public TextViewer()
        {
            this.DoubleBuffered = true;
            this.Name = "TextViewer";
            this.BackColor = System.Drawing.Color.White;
            Panel p = new Panel();
            p.BackColor = System.Drawing.Color.White;
            VScrollBar bar = new VScrollBar();
            bar.Dock = DockStyle.Right;
            bar.Scroll += new ScrollEventHandler(bar_Scroll);
            bar.Maximum = 1000;
            bar.Minimum = 0;
            p.Controls.Add(bar);
            this.Controls.Add(p);
            p.Dock = DockStyle.Fill;
            p.Paint += new PaintEventHandler(TextViewer_Paint);
            p.Resize += new EventHandler(p_Resize);
            buffer = new LinkedList<BufferLine>();
            this.WordWrapChanged += new EventHandler(OnWordWrapChanged);
        }

        private void p_Resize(object sender, EventArgs e)
        {

        }

        private void bar_Scroll(object sender, ScrollEventArgs e)
        {
            //scroll the document to specified position
            double position = e.NewValue / 1000.0;
            long offset = GetOffsetForFilePosition(position);
#if DEBUG
            System.Diagnostics.Debug.WriteLine(string.Format("Raw position = {0}; offset = {1}; file length={2}", position, offset, FileSize));
#endif
            ScrollToOffset(offset);
        }

        private void ScrollToOffset(long offset)
        {
            Offset = offset;
            ReadData();
            Display();
        }

        public override event EventHandler PercentChanged;

        #region properties
        private long FileSize { get; set; }

        /// <summary>
        /// Offset in the file. Basically a property for information, not of real use; Percentage is calculated from this
        /// </summary>
        private long Offset
        {
            get
            { return offset; }

            set
            {
                offset = value;
                PercentChanged(null, null);
            }
        }

        //private int CurrentLine { get; set; }

        public override double Percent
        {
            get
            {
                return (double)Offset / (double)FileSize * 100;
            }
        }

        /// <summary>
        /// Get count of lines displayed
        /// </summary>
        private int LineCount
        {
            get
            {
                return (int)((float)this.Height / this.Font.GetHeight()) + 1;
            }
        }

        private float LineHeight
        {
            get
            {
                return this.Font.GetHeight();
            }
        }

        private int PageSize
        {
            get
            {
                return (int)((float)this.ClientRectangle.Height / LineHeight);
            }
        }
        #endregion

        public override void Display()
        {
            Refresh();
        }

        public override void Initialize()
        {
            Reader = new UnbufferedStreamReader(FileName);
            FileSize = Reader.Length;
        }

        public override void OnKeyDown(Keys e)
        {
            switch (e)
            {
                case Keys.Down:
                    ScrollLines(1);
                    break;
                case Keys.Up:
                    ScrollLines(-1);
                    break;
                case Keys.PageUp:
                    ScrollLines(-PageSize);
                    break;
                case Keys.PageDown:
                    ScrollLines(PageSize);
                    break;
                case Keys.Home:
                    Offset = 0;
                    ReadData();
                    Display();
                    break;
                case Keys.End:
                    Offset = FileSize - 1;
                    ReadData();
                    ScrollLines(-PageSize);
                    break;
            }
        }

        /// <param name="position">Position from 0 to 1</param>
        /// <returns></returns>
        private int GetOffsetForFilePosition(double position)
        {
            return (int)((double)FileSize * position);
        }

        private void ScrollLines(int lines)
        {
            if (lines > 0)
            {
                //add lines
                for (int i = 0; i < lines; i++)
                    ReadOneLineForward();
            }
            else //scroll backwards
            {
                for (int i = 0; i > lines; i--)
                    ReadOneLineBackward();
            }
            Display();
        }

        private void ReadOneLineBackward()
        {
            var line = new BufferLine();
            Reader.Position = buffer.First.Value.Offset;
            line.Data = Reader.ReadLineBackward(out line.Offset);
            if (line.Data == null)
                return;
            buffer.RemoveLast();
            buffer.AddFirst(line);
            Offset = line.Offset;
        }

        private void ReadOneLineBackwardOld()
        {
            var line = new BufferLine();
            long startOffset = buffer.First == null ? 0 : buffer.First.Value.Offset;
            long endOffset = buffer.Last == null ? FileSize - 1 : buffer.Last.Value.Offset;
            bool ok = Reader.SeekToLastLine(startOffset, endOffset, out line.Data, out line.Offset);
            buffer.RemoveLast();
            buffer.AddFirst(line);
            Offset = line.Offset;
        }

        private void ReadOneLineForward()
        {
            buffer.RemoveFirst();
            Offset = buffer.First.Value.Offset;
            var line = new BufferLine();
            Reader.Position = buffer.Last.Value.EndOffset;
            line.Offset = Reader.Position;
            line.Data = Reader.ReadLine();
            if (line.Data == null)
                return; //TODO but we already removed the first element!!!
            buffer.AddLast(line);
        }

        private void ReadData()
        {
            //TODO do not discard the buffer on every Read, just add more stuff into it
#if DEBUG
            System.Diagnostics.Debug.WriteLine("ReadData()");
#endif
            //feed stuff to buffer
            Reader.Position = Offset;
            if (Offset != 0)
                Reader.ReadLine();
            long currentOffset = Offset;
#if DEBUG
            System.Diagnostics.Debug.WriteLine("ReadData()-current position before= " + Reader.Position);
#endif
            string line;
            buffer.Clear();
            //seek to first endline
            for (int i = 0; i < LineCount; i++)
            {
                currentOffset = Reader.Position;
                line = Reader.ReadLine();
                if (line == null)
                    break;
                buffer.AddLast(new BufferLine() { Offset = currentOffset, Data = line });
            }
#if DEBUG
            System.Diagnostics.Debug.WriteLine("ReadData()-current position after = " + Reader.Position);
#endif
        }

        private void TextViewer_Paint(object sender, PaintEventArgs e)
        {
            if (buffer.Count == 0)
                ReadData(); //this is bad
            int currentLineIndex = 0;
            foreach (var line in buffer)
            {
                if (currentLineIndex >= LineCount)
                    break;
                e.Graphics.DrawString(line.Data,
                ViewerFontManager.MonospaceFont,
                        Brushes.Black,
                        new RectangleF(
                            0,
                            LineHeight * currentLineIndex,
                            this.Width,
                            LineHeight));
                currentLineIndex++;
            }
        }

        private void OnWordWrapChanged(object sender, EventArgs e)
        {
        }

        private class BufferLine
        {
            public long Offset; //offset of this line
            public string Data; //TODO this might break on files with extremely long lines, we need to implement 'horizontal buffer' too later
            public long EndOffset
            {
                get
                {
                    return Offset + Data.Length + Environment.NewLine.Length;
                }
            }
#if DEBUG
            public override string ToString()
            {
                return Data;
            }
#endif
        }
    }
}